#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>   //注意这个是新加的

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT            1           /* 设备号个数 */
#define GPIOLED_NAME       "gpioled"       /* 名字 */
#define LEDOFF                0            /* 关灯 */
#define LEDON                 1            /* 开灯 */

/* gpioled设备结构体 */
struct gpioled_dev{
    dev_t         devid;    /* 设备号   */
    struct cdev   cdev;     /* cdev     */
    struct class  *class;   /* 类       */
    struct device *device;  /* 设备     */
    int           major;    /* 主设备号 */
    int           minor;    /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int           led_gpio; /* led使用的GPIO编号*/
};

struct gpioled_dev gpioled;    /* led设备 */

/*
 * @description   : 打开设备
 * @param - inode : 传递给驱动的inode
 * @param - filp  : 设备文件，file结构体有个叫做private_data的成员变量
 *                    一般在open的时候将private_data指向设备结构体。
 * @return        : 0 成功;其他 失败
 */
static int gpioled_open(struct inode *inode, struct file *filp)
{
    printk("gpioled open!\n");
    filp->private_data = &gpioled; /* 设置私有数据 */
    return 0;
}


/*
 * @description  : 从设备读取数据 
 * @param - filp : 要打开的设备文件(文件描述符)
 * @param - buf  : 返回给用户空间的数据缓冲区
 * @param - cnt  : 要读取的数据长度
 * @param - offt : 相对于文件首地址的偏移
 * @return       : 读取的字节数，如果为负值，表示读取失败
 */
static ssize_t gpioled_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    printk("gpioled read!\n");
    return 0;
}

/*
 * @description  : 向设备写数据 
 * @param - filp : 设备文件，表示打开的文件描述符
 * @param - buf  : 要写给设备写入的数据
 * @param - cnt  : 要写入的数据长度
 * @param - offt : 相对于文件首地址的偏移
 * @return       : 写入的字节数，如果为负值，表示写入失败
 */
static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    unsigned char databuf[1];
    unsigned char ledstat;
    
    struct gpioled_dev *dev = filp->private_data;
    
    /* 接收用户空间传递给内核的数据并且打印出来 */
    if(0 != copy_from_user(databuf, buf, cnt))
    {
        printk("kernel recevdata failed!\n");
        return -EFAULT;
    }
    
    ledstat = databuf[0];        /* 获取状态值 */

    if(ledstat == LEDON) 
    {    
        gpio_set_value(dev->led_gpio, 0);         /* 打开LED灯 */
        printk("led on!\n");
    } 
    else if(ledstat == LEDOFF) 
    {
        gpio_set_value(dev->led_gpio, 1);        /* 关闭LED灯 */
        printk("led off!\n");
    }
    
    return 0;
}


/*
 * @description   : 关闭/释放设备
 * @param - filp  : 要关闭的设备文件(文件描述符)
 * @return        : 0 成功;其他 失败
 */
static int gpioled_release(struct inode *inode, struct file *filp)
{
    printk("gpioled release！\n");
    return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner   = THIS_MODULE,    
    .open    = gpioled_open,
    .read    = gpioled_read,
    .write   = gpioled_write,
    .release = gpioled_release,
};

/*
 * @description   : LED硬件初始化(IO映射、时钟、GPIO配置)
 * @param         : 无
 * @return        : 0 成功;其他 失败
 */
static int gpioled_hardware_init(void)
{
    int ret;

    /* 获取设备树中的属性数据 */
    /* 1、获取设备节点：gpioled */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL) 
    {
        printk("gpioled node not find!\r\n");
        return -EINVAL;
    } 
    else 
    {
        printk("gpioled node find!\r\n");
    }

    /* 2、获取gpio属性, 得到LED编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
    if(gpioled.led_gpio < 0) 
    {
        printk("can't get led-gpios!\r\n");
        return -EINVAL;
    } 
    else 
    {
        printk("led-gpios num = %d\r\n", gpioled.led_gpio);
    }

    /* 3、设置GPIO为输出, 并默认关闭LED */
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if(ret < 0)
    {
        printk("can't set led-gpio!\r\n");
    }
    
    return 0;
}

/*
 * @description   : 驱动出口函数
 * @param         : 无
 * @return        : 0 成功;其他 失败
 */
static int __init gpioled_init(void)
{
    /* 初始化LED */
    int ret = gpioled_hardware_init();
    if(0 != ret)
    {
        return ret;
    }

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (gpioled.major) /* 定义了设备号 */
    {
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    } 
    else /* 没有定义设备号 */
    {
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);    /* 申请设备号 */
        gpioled.major = MAJOR(gpioled.devid);    /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid);    /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d,minor=%d\n",gpioled.major, gpioled.minor);    
    
    /* 2、初始化cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    
    /* 3、添加一个cdev */
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

    /* 4、创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class)) 
    {
        return PTR_ERR(gpioled.class);
    }

    /* 5、创建设备 */
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device)) 
    {
        return PTR_ERR(gpioled.device);
    }
    
    printk("gpioled init done!\n");
    return 0;
}

/*
 * @description   : 驱动出口函数
 * @param         : 无
 * @return        : 无
 */
static void __exit gpioled_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&gpioled.cdev);/*  删除cdev */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
    
    printk("gpioled exit done!\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(gpioled_init);
module_exit(gpioled_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zzy");
